/*
 *  Copright: 2008 Philipp Leitner, Distributed Systems Group,
 *            TU Wien, leitner@infosys.tuwien.ac.at
 *
 *  This file is part of DAIOS.
 *
 *  DAIOS is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as
 *  published by the Free Software Foundation.
 * 
 *  DAIOS is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with DAIOS.  If not, see
 *  <http://www.gnu.org/licenses/>.
 * 
 */

package at.ac.tuwien.infosys.dsg.daiosPlugins.axis2;

import java.net.URL;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.logging.Logger;

import javax.wsdl.WSDLException;
import javax.xml.namespace.QName;

import org.apache.axiom.om.OMElement;
import org.apache.axis2.AxisFault;
import org.apache.axis2.addressing.EndpointReference;
import org.apache.axis2.client.Options;
import org.apache.axis2.client.ServiceClient;
import org.apache.axis2.client.async.AsyncResult;
import org.apache.axis2.client.async.Callback;

import at.ac.tuwien.infosys.dsg.daios.exceptions.AmbigiousException;
import at.ac.tuwien.infosys.dsg.daios.exceptions.InvocationException;
import at.ac.tuwien.infosys.dsg.daios.framework.DaiosInterceptor;
import at.ac.tuwien.infosys.dsg.daios.framework.PollObject;
import at.ac.tuwien.infosys.dsg.daios.framework.interfaces.IConstants;
import at.ac.tuwien.infosys.dsg.daios.framework.interfaces.IDaiosCallback;
import at.ac.tuwien.infosys.dsg.daios.framework.interfaces.IServiceFrontendImplementor;
import at.ac.tuwien.infosys.dsg.daios.framework.structs.DaiosInputMessage;
import at.ac.tuwien.infosys.dsg.daios.framework.structs.DaiosOutputMessage;
import at.ac.tuwien.infosys.dsg.daios.wsdl.WSDLEncoding;
import at.ac.tuwien.infosys.dsg.daios.wsdl.impl.WSDL;
import at.ac.tuwien.infosys.dsg.daios.wsdl.interfaces.IWSDL;
import at.ac.tuwien.infosys.dsg.daios.wsdl.interfaces.IWSDLMessage;
import at.ac.tuwien.infosys.dsg.daios.wsdl.interfaces.IWSDLOperation;
import at.ac.tuwien.infosys.dsg.daios.wsdl.matcher.WSDLStructuralMatcher;
import at.ac.tuwien.infosys.dsg.daios.wsdl.typesystem.WSDLTypeTree;
import at.ac.tuwien.infosys.dsg.daiosPlugins.axis2.axiom.OMElementConverter;
import at.ac.tuwien.infosys.dsg.daiosPlugins.nativeInvoker.NativeServiceInvoker;

/**
 * The service invoker used to call a Web Service using the
 * Axis2 Web Service client libraries.
 * 
 * @author philipp
 *
 */
public class Axis2ServiceInvoker implements IServiceFrontendImplementor {
	
	private DaiosInterceptor interceptor = null;
	private Logger log = null;
	private IWSDL wsdl = null;
	private String endpoint = null;
	private IWSDLMessage fixedMessage = null;
	private IWSDLOperation fixedOperation = null;
	private Options axis2Options = null;
	private WSDLStructuralMatcher matcher = null;
	private IWSDLMessage outputMsgToUse = null;
	private Hashtable<QName, String> headers = null;
	
	/**
	 * Construct a new invoker that calls the service
	 * that is defined by the WSDL definition at a certain
	 * URL.
	 * 
	 * @param url The location of the service definition.
	 * 
	 * @throws WSDLException
	 * @throws AmbigiousException 
	 */
	Axis2ServiceInvoker(URL url, DaiosInterceptor interceptor)
		throws WSDLException, AmbigiousException {
		
		this.interceptor = interceptor;
		
		log = Logger.getLogger(IConstants.LOG_NAME);
		
		if(interceptor != null)
			interceptor.beginWSDLProcessing(url);
		this.wsdl = new WSDL(url);
		this.wsdl.setInterceptor(interceptor);
		if(interceptor != null)
			interceptor.finishedWSDLProcessing(url, this.wsdl);
		
		this.headers = new Hashtable<QName, String>();
		
		matcher = new
			WSDLStructuralMatcher(wsdl);
		
		// we also kickoff type parsing now
		this.wsdl.getTypes();
		
	}
	
	/**
	 * Do a synchronous, blocking, request-response style
	 * call. The client will be blocked until the return
	 * message is delivered.
	 * 
	 * @param message The Daios style input message.
	 * @return The Daios style output message.
	 * 
	 * @throws AmbigiousException
	 * @throws InvocationException
	 */		
	public DaiosOutputMessage requestResponse(DaiosInputMessage message)
		throws AmbigiousException, InvocationException {
		
		log.info("Doing request/response call with message:\n"
				+message.toString());
		
		if(interceptor != null)
			interceptor.beginInvocation("SOAP", "requestResponse",
				message, this);
		
		// prepare call (i.e. do all the matching and guessing)
		OMElement axisOp = prepareAxis2Call(message);
		
		// do blocking invocation
		OMElement result = null;
		try {
			ServiceClient sender = new ServiceClient();
			sender.setOptions(axis2Options);
			addHeaders(sender);
			if(interceptor != null)
				// TODO: serializing the OM model here slows the app !!!
				interceptor.doHTTPInvocation(endpoint,
					axisOp.toString(), this);
			result = sender.sendReceive(axisOp);
			if(interceptor != null)
				// TODO: serializing the OM model here slows the app !!!
				interceptor.receiveHTTPResult(endpoint,
						axisOp.toString(), this, result.toString());
		}
		catch(AxisFault e) {
			log.severe("There was an error while executing your "+
				"request.");
			throw new InvocationException(e);
		}
        
		// wrap result back into Message
		if(interceptor != null)
			// TODO: serializing the OM model here slows the app !!!
			interceptor.beginOutputConversion(message, outputMsgToUse,
					wsdl, this, result.toString());		
		DaiosOutputMessage returnMsg = 
			OMElementConverter.getInstance(
				wsdl.getBindingStyle(),
				outputMsgToUse, wsdl.getTypes())
			.convert(result);
        
		if(interceptor != null)
			// TODO: serializing the OM model here slows the app !!!
			interceptor.finishedOutputConversion(message, outputMsgToUse,
					wsdl, this, result.toString(), returnMsg);		
		
		if(interceptor != null)
			interceptor.finishedInvocation("SOAP", "requestResponse",
				message, this, returnMsg);
		
		log.info("Returning Message:\n"+returnMsg.toString());
		
		// return 
		return returnMsg;
		
	}

	/**
	 * 
	 * Do an asynchronous 'fire and forget'-style call.
	 * The client fires the input message, and does not
	 * expect any kind of answer or confirmation from the
	 * service.
	 * 
	 * @param message The Daios style input message.
	 * 
	 * @throws AmbigiousException
	 * @throws InvocationException
	 */	
	public void fireAndForget(DaiosInputMessage message)
		throws AmbigiousException, InvocationException {
		
		log.info("Doing fire and forget call with message:\n"
				+message.toString());
		
		if(interceptor != null)
			interceptor.beginInvocation("SOAP", "fireandforget",
				message, this);
		
		// prepare call (i.e. do all the matching and guessing)
		OMElement axisOp = prepareAxis2Call(message);
		
		// do non-blocking invocation
		try {
			ServiceClient sender = new ServiceClient();
			sender.setOptions(axis2Options);
			addHeaders(sender);
			if(interceptor != null)
				// TODO: serializing the OM model here slows the app !!!
				interceptor.doHTTPInvocation(endpoint,
					axisOp.toString(), this);
			sender.fireAndForget(axisOp);
		}
		catch(AxisFault e) {
			log.severe("There was an error while executing your "+
				"request.");
			throw new InvocationException(e);
		}
        
		if(interceptor != null)
			interceptor.finishedInvocation("SOAP", "fireandforget",
				message, this, null);
		
	}
	
	/**
	 * Do an asynchronous 'callback'-style call.
	 * The client fires the input message, and registers
	 * a <i>callback listener</i> which is notified
	 * when the response is available. The listener
	 * has to implement the interface
	 * <link>at.ac.tuwien.infosys.dsg.daios.framework.interfaces.DaiosCallback</link>
	 * and implement the <i>onComplete</i> and <i>onError</i>
	 * methods accordingly.
	 * 
	 * @param message The Daios-style input message.
	 * @param callback The registered callback listener.
	 * 
	 * @throws AmbigiousException
	 * @throws InvocationException
	 */	
	public void callback(DaiosInputMessage message, IDaiosCallback callback)
		throws AmbigiousException, InvocationException {
	
		log.info("Doing callback call with message:\n"
				+message.toString());
		
		if(interceptor != null)
			interceptor.beginInvocation("SOAP", "callback",
				message, this);
		
		// prepare call (i.e. do all the matching and guessing)
		OMElement axisOp = prepareAxis2Call(message);
		
		// create callback wrapper
		DaiosStdCallbackHandler wrapper =
			new DaiosStdCallbackHandler(callback,
					wsdl.getBindingStyle(),
					outputMsgToUse, wsdl.getTypes(), interceptor);
		
		// do non-blocking callback invocation
		try {
			ServiceClient sender = new ServiceClient();
			sender.setOptions(axis2Options);
			addHeaders(sender);
			if(interceptor != null)
				// TODO: serializing the OM model here slows the app !!!
				interceptor.doHTTPInvocation(endpoint,
					axisOp.toString(), this);
			sender.sendReceiveNonBlocking(axisOp,wrapper);
		}
		catch(AxisFault e) {
			log.severe("There was an error while executing your "+
				"request.");
			throw new InvocationException(e);
		}
		
	}
	
	/**
	 * Do an asynchronous 'poll object'-style call. The client
	 * fires the input message, and receives a 'poll object'
	 * as response. This poll object can be 'queried' for the
	 * result of the invocation as soon as it is available.
	 * 
	 * <p>
	 * 
	 * The poll object call is an abstraction of the more
	 * general callback call.
	 * 
	 * @param message The Daios-style input message.
	 * @return The poll object which will contain the result
	 * as soon as it is available.
	 * 
	 * @throws AmbigiousException
	 * @throws InvocationException
	 */		
	public PollObject pollObjectCall(DaiosInputMessage message)
		throws AmbigiousException, InvocationException {
	
		log.info("Doing Poll Object call with message:\n"
				+message.toString());
		
		if(interceptor != null)
			interceptor.beginInvocation("SOAP", "pollobject",
				message, this);
		
		// prepare call (i.e. do all the matching and guessing)
		OMElement axisOp = prepareAxis2Call(message);

		// create a poll object for the client
		Axis2PollObject po = new Axis2PollObject();
		
	    // create callback handler for Axis2
		Callback callback =
			new DaiosPOCallbackHandler(po,
					wsdl.getBindingStyle(),
					outputMsgToUse, wsdl.getTypes(), interceptor);
		
		// do non-blocking invocation
		try {
			ServiceClient sender = new ServiceClient();
			sender.setOptions(axis2Options);
			addHeaders(sender);
			if(interceptor != null)
				// TODO: serializing the OM model here slows the app !!!
				interceptor.doHTTPInvocation(endpoint,
					axisOp.toString(), this);
			sender.sendReceiveNonBlocking(axisOp,callback);
		}
		catch(AxisFault e) {
			log.severe("There was an error while executing your "+
				"request.");
			throw new InvocationException(e);
		}
		
		// return poll object
		return po;
		
	}	

	/**
	 * Set the endpoint address of the service. It is not
	 * mandatory to set the endpoint using this method -
	 * if it is not set the endpoint will be read from
	 * the WSDL definition. If the defintion contains more
	 * than one endpoint address usage of this method <i>is</i>
	 * mandatory.
	 * 
	 * @param endpoint The address of the endpoint.
	 */
	public void setEndpointAddress(URL endpoint) {
		this.endpoint = endpoint.toExternalForm();
	}
	
	/**
	 * Hard-set the name of the WSDL operation to use.
	 * 
	 * @param operation The QName of the operation to use.
	 * 
	 * @throws AmbigiousException 
	 */
	public void setOperation(QName operation) throws AmbigiousException {
		this.fixedOperation = wsdl.getOperation(operation);
		if(this.fixedOperation == null)
			throw new AmbigiousException("Operation "
					+operation.getLocalPart()+" not found.");		
	}
	
	/**
	 * Hard-set the name of the input message to use.
	 * 
	 * @param message The QName of the input message to use.
	 * 
	 * @throws AmbigiousException
	 */	
	public void setInputMessage(QName message) throws AmbigiousException {
		this.fixedMessage = wsdl.getMessage(message);
		if(this.fixedMessage == null)
			throw new AmbigiousException("Message "
					+message.getLocalPart()+" not found.");
	}
	
	/**
	 * Set a list of specific Axis2 options to use. These
	 * options will be used in addition to the default options
	 * for the following invocation(s).
	 * 
	 * @param options The Axis2 options to set.
	 */	
	public void setAxis2Options(Options options) {
		this.axis2Options = options;
	}
	
	/**
	 * Gets the currently set Axis2 options.
	 * 
	 * @return options The current Axis2 options.
	 */	
	public Options getAxis2Options() {
		return axis2Options;
	}
	
	/**
	 * Add a certain SOAP header to the invocation.
	 * 
	 * @param header The QName of the header to set.
	 * @param value The String value of the header.
	 * @return For simplicity this method returns the invoker.
	 */
	public Axis2ServiceInvoker addHeader(QName header, String value)
		throws AxisFault {
		
		// test if this is a legal header
		// this is a little dirty :-/
		new ServiceClient().addStringHeader(header, value);
		
		headers.put(header, value);
		
		return this;
		
	}
	
	/**
	 * Remove a certain SOAP header.
	 * 
	 * @param header The QName of the header to remove.
	 * @return For simplicity this method returns the invoker.
	 */	
	public Axis2ServiceInvoker removeHeader(QName header) {
		headers.remove(header);
		return this;
	}
	
	/**
	 * Get a list of all current header QNames.
	 * 
	 * @return A list of all header QNames.
	 */
	public Enumeration<QName> getHeaders() {
		return headers.keys();
	}
	
	/**
	 * Hook an interceptor into this frontend.
	 * 
	 * @param interceptor
	 */	
	public void setInterceptor(DaiosInterceptor interceptor) {
		this.interceptor = interceptor;
	}
	
	/**
	 * Prepare an Axis2 call. Construct an OMElement that
	 * can be fired using Axis2.
	 * 
	 * <p>
	 * 
	 * This method carries out a couple of steps:
	 * 
	 * <pre>
	 *   -- first of all, an endpoint is figured out.
	 *   -- after that the input message is compared
	 *      with the messages in the WSDL file, and
	 *      a matching WSDL message is chosen.
	 *   -- then an operation is chosen that has this
	 *      WSDL message as input message.
	 *   -- and finally operation and message are
	 *      converted to Axis2 format and an
	 *      OMElement is created and returned.
	 * </pre>
	 * 
	 * @param message The Daios style input message.
	 * @return The OMElement that can be used by Axis2.
	 * 
	 * @throws AmbigiousException
	 */
	private OMElement prepareAxis2Call(DaiosInputMessage message)
		throws AmbigiousException {

		// check if we have a endpoint set, otherwise
		// figure it out
		if(endpoint == null)
			endpoint = matcher.getEndpoint();
		
		if(axis2Options == null)
			axis2Options = new Options();
		axis2Options.setTo(new EndpointReference(endpoint));
		log.info("Using endpoint: "+endpoint);
		
		IWSDLMessage msgToUse = null;
		// if we do not know which input message to use
		// figure out which input message in the WSDL fits best
		// to our message
		if(fixedMessage == null) {
			
			// if we also have no operation we figure out these details from WSDL
			if(fixedOperation == null)
				msgToUse = matcher.getSimilarMsg(message);
			// otherwise we use the input message of this operation
			else {
				msgToUse = wsdl.getMessage(new QName(null,fixedOperation.getInput()));
				matcher.checkCompliance(msgToUse,message);
			}
			
			if(msgToUse == null)
				throw new AmbigiousException("The service does not accept a message with "+
					"such a signature.");
		}
		// otherwise we check the hardset message for compliance
		// with the given input
		else {
			msgToUse = fixedMessage;
			matcher.checkCompliance(msgToUse, message);
		}
			
			
		log.info("Using message: "+msgToUse.getMessageName());
		
		IWSDLOperation opToUse = null;
		// if we do not know which operation to use
		// figure out which method takes this input message
		if(fixedOperation == null) {
			opToUse = matcher.getOperation(msgToUse);
	
			if(opToUse == null)
				throw new AmbigiousException("The service currently does not support" +
					"such a message.");		
		}
		// otherwise just use this operation
		else {
			opToUse = fixedOperation;
			// check if this op and message fit together
			if(!opToUse.getInput().equals(msgToUse.getMessageName()))
				throw new AmbigiousException("Operation "+opToUse.getName()+
					" does not accept message "+msgToUse.getMessageName()+
					" as input message.");						
		}
		
		log.info("Using operation: "+opToUse.getName());
		
		outputMsgToUse = null;
		// now figure out the output message of this operation
		String outname = opToUse.getOutput();
		for(IWSDLMessage msg : wsdl.getMessages()) {
			if(msg.getMessageName().equals(outname)) {
				outputMsgToUse = msg;
				break;
			}
				
		}
		
		log.info("Expected output message: "+outputMsgToUse.getMessageName());	
		
		if(interceptor != null)
			interceptor.beginInputConversion(message, opToUse,
					msgToUse, wsdl, this);		
		
		// convert that operation and message to Axis2 format
		OMElementConverter converter =
			OMElementConverter.getInstance(
				wsdl.getBindingStyle(),
				outputMsgToUse, wsdl.getTypes());
		OMElement bodyAsOM =
			converter.convert(opToUse, msgToUse, message, wsdl.getTypes());
		
		if(interceptor != null)
			// TODO: serializing the OM model here slows the app !!!
			interceptor.finishedInputConversion(message, opToUse,
					msgToUse, wsdl, this, bodyAsOM.toString());		
		
		// return
		return bodyAsOM;
		
	}
	
	/**
	 * Add all user-defined SOAP headers to a given Axis2 service
	 * client.
	 * 
	 * @param client
	 * @throws AxisFault
	 */
	private void addHeaders(ServiceClient client) throws AxisFault {
		
		QName header = null;
		Enumeration<QName> headers = this.headers.keys();
		while(headers.hasMoreElements()) {
			header = headers.nextElement();
			client.addStringHeader(header, this.headers.get(header));
		}
		
	}
	
	/**
	 * Convert an asynchronously received document into a Daios message.
	 * For some reason we sometimes receive different messages from
	 * Axis2 depending on whether we use synchronous or asynchronous
	 * invocations. This might be a bug in Axis2.
	 * 
	 * @param result The asynchronous result to convert
	 * @param style The SOAP invocation style
	 * @param output The output message used to convert the response
	 * @param types the type tree from the WSDL definition file
	 * @return The converted Daios message.
	 */
	private DaiosOutputMessage convertAsyncResponse(OMElement result,
			WSDLEncoding style, IWSDLMessage output, WSDLTypeTree types) {
		
		DaiosOutputMessage returnMsg = null;
		returnMsg = 
			OMElementConverter.getInstance(style, output, types)
				.convertAsync(result);
		log.info("Received message:\n"+returnMsg);
		
		return returnMsg;
		
	}
	
	/**
	 * A callback handler for Axis2 used for Daios Poll Object
	 * invocations.
	 * 
	 * @author philipp
	 *
	 */
	class DaiosPOCallbackHandler extends Callback {
		
		private Axis2PollObject po = null;
		private WSDLEncoding style = null;
		private IWSDLMessage outputMsg = null;
		private WSDLTypeTree types = null;
		private DaiosInterceptor interceptor = null;
		
		/**
		 * Protected constructor. Create a new callback handler
		 * wrapped around a PollObject.
		 * 
		 * @param po The Poll Object that is going to be notified
		 * when the callback handler receives a result.
		 * @param style The encoding used for this Web Service invocation
		 * @param output The output message to use
		 * @param types The type tree from the original WSDL definitions file
		 * @param interceptor 
		 */
		DaiosPOCallbackHandler(Axis2PollObject po, WSDLEncoding style,
				IWSDLMessage output, WSDLTypeTree types,
				DaiosInterceptor interceptor) {
			this.po = po;
			this.style = style;
			this.outputMsg = output;
			this.types = types;
			this.interceptor = interceptor;
		}
		
		/**
		 * This is the callback method that will get notified
		 * when the invocation ended <i>successfully</i>. In
		 * that case the handler will notify the wrapped Poll
		 * Object.
		 * 
		 * @param result The result of the successful
		 * invocation in Axis2 notion
		 */		
		public void onComplete(AsyncResult result) {
			
			if(interceptor != null)
				// TODO: serializing the OM model here slows the app !!!
				interceptor.receiveHTTPResult(endpoint,
					null, Axis2ServiceInvoker.this,
					result.getResponseEnvelope().toString());
			
			if(interceptor != null)
				// TODO: serializing the OM model here slows the app !!!
				interceptor.beginOutputConversion(null, outputMsgToUse,
						wsdl, Axis2ServiceInvoker.this,
						result.getResponseEnvelope().toString());		
			
			DaiosOutputMessage returnMsg = 
				convertAsyncResponse(result.getResponseEnvelope(),
					style, outputMsg, types);
			
			if(interceptor != null)
				// TODO: serializing the OM model here slows the app !!!
				interceptor.finishedOutputConversion(null, outputMsgToUse,
						wsdl, Axis2ServiceInvoker.this,
						result.getResponseEnvelope().toString(),
						returnMsg);	
			
			if(interceptor != null)
				interceptor.finishedInvocation("SOAP", "pollobject",
					null, Axis2ServiceInvoker.this, returnMsg);
			
			po.setResult(returnMsg);
		}

		/**
		 * This is the callback method that will get notified
		 * when the invocation ended <i>unsuccessfully</i>, i.e.
		 * with an error. The handler will notify the wrapped Poll
		 * object about the error. 
		 * 
		 * @param e The exception that has been thrown in the course
		 * of the invocation.
		 */			
		public void onError(Exception e) {
			
			log.info("There was an error while executing:\n"+e.getMessage());
			
			po.setError(e);
			
			e.printStackTrace();
			
		}

	}
	
	/**
	 * A callback handler for Axis2 used for Daios callback
	 * invocations (PollObject invocations are using a
	 * different callback handler).
	 * 
	 * @author philipp
	 *
	 */
	class DaiosStdCallbackHandler extends Callback {
		
		private IDaiosCallback callback = null;
		private WSDLEncoding style = null;
		private IWSDLMessage outputMsg = null;
		private WSDLTypeTree types = null;
		private DaiosInterceptor interceptor = null;
		
		/**
		 * Protected constructor. Create a new callback handler
		 * wrapped around a IDaiosCallback.
		 * 
		 * @param callback The Daios callback handler. We will
		 * just forward every message we receive in this Axis2
		 * handler to the Daios handler.
		 * @param style The encoding used for this Web Service invocation
		 * @param output The output message to use
		 * @param types The type tree from the original WSDL definitions file
		 */
		DaiosStdCallbackHandler(IDaiosCallback callback, WSDLEncoding style,
				IWSDLMessage output, WSDLTypeTree types, DaiosInterceptor interceptor) {
			this.callback = callback;
			this.style = style;
			this.outputMsg = output;
			this.types = types;
			this.interceptor = interceptor;
		}
		
		/**
		 * This is the callback method that will get notified
		 * when the invocation ended <i>successfully</i>. In
		 * that case the handler will notify the underlying
		 * Daios handler.
		 * 
		 * @param result The result of the successful
		 * invocation.
		 */		
		public void onComplete(AsyncResult result) {
			
			if(interceptor != null)
				// TODO: serializing the OM model here slows the app !!!
				interceptor.receiveHTTPResult(endpoint,
					null, Axis2ServiceInvoker.this,
					result.getResponseEnvelope().toString());
			
			if(interceptor != null)
				// TODO: serializing the OM model here slows the app !!!
				interceptor.beginOutputConversion(null, outputMsgToUse,
						wsdl, Axis2ServiceInvoker.this,
						result.getResponseEnvelope().toString());	
			
			// convert result back
			DaiosOutputMessage returnMsg =
				convertAsyncResponse(result.getResponseEnvelope(),
						style, outputMsg, types);
			
			if(interceptor != null)
				// TODO: serializing the OM model here slows the app !!!
				interceptor.finishedOutputConversion(null, outputMsgToUse,
						wsdl, Axis2ServiceInvoker.this,
						result.getResponseEnvelope().toString(), returnMsg);	
			
			if(interceptor != null)
				interceptor.finishedInvocation("SOAP", "callback",
					null, Axis2ServiceInvoker.this, returnMsg);
			
			// notify client callback handler
			callback.onComplete(returnMsg);
			
		}

		/**
		 * This is the callback method that will get notified
		 * when the invocation ended <i>unsuccessfully</i>, i.e.
		 * with an error. The handler will notify the wrapped callback
		 * handler about the error. 
		 * 
		 * @param e The exception that has been thrown in the course
		 * of the invocation.
		 */		
		public void onError(Exception e) {
			
			log.severe("There was an error while executing:\n"+e.getMessage());
			
			// notify client callback handler
			callback.onError(e);
			
		}
	}

}
